#define _POSIX_SOURCE
#define _XOPEN_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wait.h>
#include <signal.h>
#include <pthread.h>
#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/shm.h>
#include "libmem.h"
#include "liblist.h"
#include "libqueue.h"
#include "libtabla.h"
#include "defines.h"
#include "liblog.h"
#include "libproceso.h"

#define MAXCMD 30

void cargarConfiguracion();
void inicializarListas();
void inicializarThreads();
void inicializarThIOT(pthread_t*);
void liberarRecursos();

void ejecutandoPid(int);
void finalizadaEjecucionPid(int);
int pidEjecutandoIO();

//Funcion a llamar al recibir la señal USR2
void logueoEstadoListas();

void fnSTT(); 	//funcion operatoria del thread STT
void fnPROC();	//funcion operatoria del thread PRT
void fnIOT();	//funcion operatoria del thread IOT  
void fnLOG();	//funcion operatoria del thread LOG

Lista* listaNuevos = NULL;
Lista* listaExSuspendidos = NULL;
Lista* listaSuspendidosListos = NULL;
Lista* listaListos = NULL;
Lista* listaBloqueados = NULL;
Lista* listaIOTerminada = NULL;
Tabla* tablaProcesos = NULL;
int* listPidXIO = NULL;		//Puntero a lista de procesos ejecutando IO

pthread_t thSTT;
pthread_t thPROC;
pthread_t thLOG;

int check_log = 0;
int pidMLP;

int cantIOT;			//Cantidad de instancias del hilo IOT
char* ordenamientoLista; 	//Ordenamiento de la Lista de Listos
int delaySegInstCPU; 	//Intervalo de tiempo en segundos que insume cada instrucción de CPU
int delaySegInstIO; 	//Intervalo de tiempo en segundos que insume cada instrucción de I/O
int delaySegSTT;		//Tiempo en segundos que le insume al STT llevar un proceso de una lista a otra
char* pathImagen;
int cantMaxProcesos;

int main(int argc, char **argv) {
	sigset_t signal_set;
	int i;
	infoMsj mensaje;
	
	if(argc < 10 ){
		perror("Faltan parámetros\n");
		exit(EXIT_FAILURE);
	}

	//Bloqueo todas las señales
    	sigfillset( &signal_set );
	pthread_sigmask( SIG_BLOCK, &signal_set, NULL );
                	
	//Leo la configuración
	cargarConfiguracion();
	
	//Leo las posiciones de la memoria compartida y la cantidad de elementos maxima que pueden tener las listas
	int shNuevos = atoi(argv[1]);
	int shExSusp = atoi(argv[2]);
	int shSuspLis = atoi(argv[3]);
	int shListos = atoi(argv[4]);
	int shBloque = atoi(argv[5]);
	int shIOTerm = atoi(argv[6]);
	int shTabla = atoi(argv[7]);
	int cantMaxElem = atoi(argv[8]);
	cantMaxProcesos = cantMaxElem;
	int mailbox = atoi(argv[9]);
	pidMLP = atoi(argv[10]);
	long unsigned int pthread;
	int exit = 0;
	
	//Abro todas las listas de memoria compartida
	inicializarListas(shNuevos, shExSusp, shSuspLis, shListos, shBloque, shIOTerm, shTabla, cantMaxElem);
	//Abro la el Mailbox de donde recibir y mandar mensajes
	abrirCola(mailbox);
	//Lanzo los threads del PCP menos los IOT
	inicializarThreads();
	
	//Creo los threads de IOT que voy a tener disponibles
	pthread_t thIOT[cantIOT];
	//Creo un array donde voy a marcar que procesos se estan ejecutando
	listPidXIO = (int*)reservar(sizeof(int)*cantIOT);

	inicializarThIOT(thIOT);
	
	while(!exit) {
		mensaje = recibirMsj(PCP);
		if(mensaje.action == PROC_TERMINADO) {
			pthread = atol(mensaje.data);		
			loguear_program_finish (mensaje.pid, tablaProcesos,"PCP",thPROC,"PROC","INFO");
			borrarProceso(tablaProcesos,mensaje.pid);
			enviarMsj(PST, PST_DECREMENTAR_MMP, 0, NULL);
			enviarMsj(PST, PST_DECREMENTAR_MPS, 0, NULL);
		}
		if(mensaje.action == SUSPENDER_PROC) {
			int pidIO, i;
			int cantProcSusp = atoi(mensaje.data);
			for (i = 0; i < cantProcSusp; i++){
				//Busco un proceso que este ejecuanto IO
				pidIO = pidEjecutandoIO();
				if (pidIO >= 0){
					//Suspendo al proceso
					serializar(getProceso(tablaProcesos, pidIO), getName(tablaProcesos, pidIO), pathImagen);
					//Registro el decremento de MMP
					cambiarEstadoMemoria(tablaProcesos, pidIO, 0);
					enviarMsj(PST, PST_DECREMENTAR_MMP, 0, NULL);
					
				}
			}
		}
		if(mensaje.action == LOG_STATE) {
			if (check_log == 0) {
				loguearPCP ( listaListos, listaExSuspendidos, listaSuspendidosListos, tablaProcesos, "INFO");
			}else
				check_log = 0;
		}
		else if(mensaje.action == CHANGE_TIME_CPU) {
			delaySegInstCPU = atoi(mensaje.data);
		}
		else if(mensaje.action == CHANGE_TIME_IO) {
			delaySegInstIO = atoi(mensaje.data);
		}
		else if(mensaje.action == CHANGE_TIME_TRAVEL_STT) {
			delaySegSTT = atoi(mensaje.data);
		}						
		else if(mensaje.action == EXIT) {
			exit=1;
		}
	}

	//Espero la finalización de los threads
	pthread_join(thSTT, NULL);
	pthread_join(thPROC, NULL);	
	for (i = 0; i < cantIOT; i++ ){
		pthread_join(thIOT[i], NULL);
	}
	pthread_join(thLOG, NULL);	

	//Retiro de memoria todas las listas en memoria compartida y cualquier otra memoria reservada
	liberarRecursos();

	return EXIT_SUCCESS;
}

void cargarConfiguracion(){

	
	ordenamientoLista = (char*)reservar(strlen(getenv("ORD_LISTOS"))+1);
	pathImagen = (char*)reservar(strlen(getenv("PATH_SUSPEND"))+2);

	strcpy(ordenamientoLista, getenv("ORD_LISTOS"));
	cantIOT = atoi(getenv("INSTANCIAS_IOT")); 
	delaySegInstCPU = atoi(getenv("TIME_CPU"));
	delaySegInstIO = atoi(getenv("TIME_IO"));
	delaySegSTT = atoi(getenv("TIME_TRAVEL_STT"));
	strcpy(pathImagen, getenv("PATH_SUSPEND"));
	if ('/' != pathImagen[strlen(pathImagen)-1]){
		pathImagen[strlen(pathImagen)]='/';
		pathImagen[strlen(pathImagen)+1]='\0';
	}
} 

void inicializarListas(int shNuevos, int shExSusp, int shSuspLis, int shListos, int shBloque, int shIOTerm, int shTabla, int cantMaxElem){
	int retsh;

	listaNuevos = (Lista*)reservar(sizeof(Lista));
	listaExSuspendidos = (Lista*)reservar(sizeof(Lista));
	listaSuspendidosListos = (Lista*)reservar(sizeof(Lista));
	listaListos = (Lista*)reservar(sizeof(Lista));
	listaBloqueados = (Lista*)reservar(sizeof(Lista));
	listaIOTerminada = (Lista*)reservar(sizeof(Lista));	
	tablaProcesos = (Tabla*)reservar(sizeof(Tabla));	
		
	retsh = abrirLista(listaNuevos, cantMaxElem, shNuevos);
	if (retsh == -1){
		perror("Error accediendo a la lista Nuevos, ejecución abortada.");
		exit(EXIT_FAILURE);
	}
	retsh = abrirLista(listaExSuspendidos, cantMaxElem, shExSusp);
	if (retsh == -1){
		perror("Error accediendo a la lista Exsuspendidos, ejecución abortada.");
		exit(EXIT_FAILURE);
	}
	retsh = abrirLista(listaSuspendidosListos, cantMaxElem, shSuspLis);
	if (retsh == -1){
		perror("Error accediendo a la lista Suspendidos/Listos, ejecución abortada.");
		exit(EXIT_FAILURE);
	}
	retsh = abrirLista(listaListos, cantMaxElem, shListos);
	if (retsh == -1){
		perror("Error accediendo a la lista Listos, ejecución abortada.");
		exit(EXIT_FAILURE);
	}
	retsh = abrirLista(listaBloqueados, cantMaxElem, shBloque);
	if (retsh == -1){
		perror("Error accediendo a la lista bloqueados, ejecución abortada.");
		exit(EXIT_FAILURE);
	}
	retsh = abrirLista(listaIOTerminada, cantMaxElem, shIOTerm);
	if (retsh == -1){
		perror("Error accediendo a la lista IOTerminada, ejecución abortada.");
		exit(EXIT_FAILURE);
	}
	retsh = abrirTabla(tablaProcesos, cantMaxElem, shTabla);
	if(retsh == -1){
		perror("Error creando la tabla de Procesos");
		exit(EXIT_FAILURE);
	}
}

void inicializarThreads(){
	int retth;
	//Inicializo el thread STT
	retth = pthread_create(&thSTT, NULL, (void *)fnSTT, NULL);
	if(retth) {
		perror("Error iniciando thread STT, ejecución abortada.");
		exit(EXIT_FAILURE);
	}
	//Inicializo el thread PROC
	retth = pthread_create(&thPROC, NULL, (void *)fnPROC, NULL);
	if(retth) {
		perror("Error iniciando thread PROC, ejecución abortada.");
		exit(EXIT_FAILURE);
	}
	//Inicializo el thread LOG
	retth = pthread_create(&thLOG, NULL, (void *)fnLOG, NULL);
	if(retth) {
		perror("Error iniciando thread LOG, ejecución abortada.");
		exit(EXIT_FAILURE);
	}
}

void inicializarThIOT(pthread_t* thIOT){
	int i, retth;
	for (i = 0; i < cantIOT; i++ ){
		//Inicializo el thread IOT
		retth = pthread_create(&thIOT[i], NULL, (void *)fnIOT, NULL);
		if(retth) {
			perror("Error iniciando thread IOT, ejecución abortada.");
			exit(EXIT_FAILURE);
		}
		//Marco la lista de Procesos ejecutando vacia.
		listPidXIO[i] = -1;
	}
}

void liberarRecursos(){
	liberar(listaNuevos);
	liberar(listaExSuspendidos);
	liberar(listaSuspendidosListos);
	liberar(listaListos);
	liberar(listaBloqueados);
	liberar(listaIOTerminada);	
}

void fnSTT(){
	infoMsj mensaje;
	//Las filas son los procesos y las columnas el PID y el SOURCE
	int pids[cantMaxProcesos][2];
	int pid = 0;
	int source;
	Proceso * p;
	int i,pidsEncontrados;
	while(1){
		mensaje = recibirMsj(STT);
		//Me avisan que puedo hay un proceso en alguna de las 3 listas
		if (mensaje.action == PROC_DISPONIBLE){
			lockLista(listaIOTerminada);
			lockLista(listaExSuspendidos);
			lockLista(listaNuevos);
			//Borro todos los mensajes que pudieron haber llegado hasta este momento
			dumpMsj(STT);
			//Recorro las listas tratando de conseguir un proceso
			i=0;
			while((pid = retirarElemento(listaIOTerminada) ) > 0) {
				pids[i][0] = pid;
				pids[i][1] = IOTERM;
				i++;
			}
			while((pid = retirarElemento(listaExSuspendidos) ) > 0) {
				pids[i][0] = pid;
				pids[i][1] = EXSUSP;
				i++;
			}
			while((pid = retirarElemento(listaNuevos) ) > 0) {
				pids[i][0] = pid;
				pids[i][1] = NUEVO;
				i++;
			}
			
			unlockLista(listaIOTerminada);
			unlockLista(listaExSuspendidos);
			unlockLista(listaNuevos);
			
			//Si obtuve un proceso, lo muevo a la lista Listos
			if (pids[0][0] >= 0){
				pidsEncontrados = i;
				lockLista(listaListos);
				for(i=0;i < pidsEncontrados; i++) {
					pid = pids[i][0];
					source = pids[i][1];
					cambiarEstado(tablaProcesos, pid, LISTO);	//le cambio el estado del proceso

					if(!enMemoria(tablaProcesos,pid)) {
							if(suspendido(tablaProcesos,pid,pathImagen)) {
								//Deserealiza el codigo y lo asigna en la tabla de procesos
								setProcesoCodigo(tablaProcesos,pid,deserealizar(getName(tablaProcesos,pid), pathImagen));
								cambiarEstadoMemoria(tablaProcesos, pid, 1);
							}
							else {
								//Crea un nuevo codigo a partir del archivo fuente.
								if ((p = nuevoProceso(getName(tablaProcesos,pid))) == NULL) {
									printf("Error al abrir archivo\n");
								}else {
									setProcesoCodigo(tablaProcesos,pid,p);
								}
							}
						}
					sleep(delaySegSTT);	//Cantidad de tiempo que le insume al STT mover elementos a la lista
					//Calcula el timestamp nuevo
					resetTimeStamp(tablaProcesos,pid);
						//Lo inserta FIFO
					insertarElemento(listaListos,pid);
					loguear_change_list(pid, source, LISTO, tablaProcesos, "PCP", thSTT, "STT", "INFO");
				}
				
				if(strstr(ordenamientoLista,"HRRN")){
						//Los ordena
					ordenarElementos(tablaProcesos,listaListos,delaySegInstCPU,hrrn);
				}
				else if(strstr(ordenamientoLista,"SPN")) {
						//Los ordena
					ordenarElementos(tablaProcesos,listaListos,delaySegInstCPU,spn);
				}
				else {
						 //FIFO: No hace nada porque ya están ordenados fifo cuando se insertan.
				}
				unlockLista(listaListos);
				//Mando un mensaje por cada proceso insertado en Listos
				for(i=0;i < pidsEncontrados; i++) {
					//Le aviso al PROCESADOR que hay un proceso en la lista Listos
					enviarMsj(PROC, PROC_DISPONIBLE_LISTOS, 0, NULL);
				}
			}
			
		}
	}
	
}

void fnPROC(){
	infoMsj mensaje;
	int pid;
	int io;
	int finProceso;
	
	while(1){
		pid=0;
		mensaje = recibirMsj(PROC);
		if(mensaje.action == PROC_DISPONIBLE_LISTOS) {
			lockLista(listaListos);
				pid = retirarElemento(listaListos);
			unlockLista(listaListos);
			if (pid >= 0 ) {
				io = -1; // -1 Significa que no hay IO
				finProceso = 0;
				while(io < 0 && !finProceso) {
					io=-1; //Resetea el valor del io
					finProceso=0; //Resetea valor de finProceso;
					finProceso = procesar(getProceso(tablaProcesos,pid),delaySegInstCPU, delaySegInstIO, &io);
				}
				if(finProceso) {
					enviarMsj(PCP,PROC_TERMINADO,pid,NULL);
				}
				if(io >= 0 ) { //IO = 0 es un io(); sin parametro
					char tiempo[10];
					sprintf(tiempo,"%d",io);
					cambiarEstado(tablaProcesos, pid, BLOQ);	//le cambio el estado del proceso
					lockLista(listaListos);
						insertarElemento(listaBloqueados, pid);
					unlockLista(listaListos);					
					//Le aviso a los IOT que hay un proceso en la lista Bloqueados
					enviarMsj(IOT, PROC_DISPONIBLE_BLOQUEADOS, 0, tiempo);
					loguear_change_list(pid, LISTO, BLOQ, tablaProcesos, "PCP", thPROC, "PROC", "INFO");					
				}
			}
		}
	}	
}

void fnIOT(){
	infoMsj mensaje;
	int pid,estadoMem;
	pthread_t pidIOT;
	pidIOT = pthread_self();
	
	while(1){
		mensaje = recibirMsj(IOT);
		if (mensaje.action == PROC_DISPONIBLE_BLOQUEADOS){
			int tsegExec;
			
			//printf("Recibido mensaje para sacar un elemento de la lista de Bloqueados\n");
			lockLista(listaBloqueados);
				pid = retirarElemento(listaBloqueados);
			unlockLista(listaBloqueados);
			if (pid >= 0){
				//Marco el pid, como que esta en ejecucion
				ejecutandoPid(pid);

				//Ejecuto la IO y consumo el tiempo necesario
				tsegExec = atoi(mensaje.data);
				sleep(tsegExec);

				//Marco el pid como ya termino la E/S
				finalizadaEjecucionPid(pid);

				//Verifico el estado en memoria del proceso
				estadoMem = enMemoria(tablaProcesos, pid);
				if (estadoMem == -1){
					perror("Error leyendo estado de memoria del proceso");
					exit(EXIT_FAILURE);
				}
				//Si el proceso se encuentra en memoria real lo muevo a la lista de IOTerminada, sino a Susp/Listos
				if (estadoMem == 1){
					cambiarEstado(tablaProcesos, pid, IOTERM);
					lockLista(listaIOTerminada);
						insertarElemento(listaIOTerminada, pid);
					unlockLista(listaIOTerminada);
					enviarMsj(STT, PROC_DISPONIBLE, 0, NULL);
					loguear_change_list(pid, BLOQ, IOTERM, tablaProcesos, "PCP", pidIOT, "IOT", "INFO");
					
				}else{
					cambiarEstado(tablaProcesos, pid, SUSPLISTO);
					lockLista(listaSuspendidosListos);
						insertarElemento(listaSuspendidosListos, pid);
					unlockLista(listaSuspendidosListos);
					enviarMsj(PRT, PROC_DISPONIBLE_SUSPLIST, 0, NULL);
					loguear_change_list(pid, BLOQ, SUSPLISTO, tablaProcesos, "PCP", pidIOT, "IOT", "INFO");
									
				}
			}
		}
	}
}

void fnLOG(){
	sigset_t signal_set;
	int sig;	
	//signal(SIGUSR2, logueoEstadoListas);
    while(1){
		/* espero por una señal */
		sigfillset( &signal_set );
		sigwait( &signal_set, &sig );
 
		switch( sig ){
			case SIGUSR2:
				logueoEstadoListas();
		  		break;
		}
    }
}

void ejecutandoPid(int pid){
	int i;
	for(i = 0; i < cantIOT; i++){
		//Si encuentro un slot disponible, meto el pid
		if (listPidXIO[i] == -1)
			listPidXIO[i] = pid;
	}
}

void finalizadaEjecucionPid(int pid){
	int i;
	for(i = 0; i < cantIOT; i++){
		//libero el slot del pid que termino de ejecucion
		if (listPidXIO[i] == pid)
			listPidXIO[i] = -1;
	}	
}

int pidEjecutandoIO(){
	int i; 
	int encontrado = 0;
	int rpid = -1;
	for(i = 0; i < cantIOT && !encontrado; i++){
		//Si el proceso no esta suspendido ya, entonces lo devuelvo
		if (listPidXIO[i] != -1 && !suspendido(tablaProcesos, listPidXIO[i], pathImagen)){
			rpid = listPidXIO[i];
			encontrado = 1;
		}
	}	
	return rpid;
}

void logueoEstadoListas(){
	if (check_log == 0) {
		check_log = 1;
		loguearPCP ( listaListos, listaExSuspendidos, listaSuspendidosListos, tablaProcesos, "INFO");
		kill(pidMLP, SIGUSR2);
	}else	
		check_log = 0;
}
